If you are working on an application, whether mobile or web, in Flutter, on Android, in Vue.js or React.js, you need an API to obtain the data. GraphQL is one of the most popular and modern technologies for building an API. In a GraphQL query, you can get what you need in a more readable way. There are three kinds of query:
Query: Read the data.
Mutation: Insert, update or delete the data.
Subscription: A query that keeps listening for a change.
The Hasura GraphQL engine is an open source technology that allows you to quickly create a GraphQL server over PostgreSQL. You can make any kind of GraphQL queries, add rules to allow some users to make specific queries, run serverless functions, and much more. If you want to know everything Hasura can do, you should read this [Article](https://blog.hasura.io/what-is-hasura-ce3b5c6e80e8. Also, you can read or contribute to Hasura’s code in its Github repository.
In this tutorial, we will learn to make all kinds of GraphQL queries in Hasura, which will help you realize you should stop using PostgreSQL alone and start using Hasura as a better way to make your API.
All code shown in this tutorial is in this Github repository.
Here is the Hasura server, with which this tutorial was made.
It’s simple if you should follow this short tutorial: https://docs.hasura.io/1.0/graphql/manual/getting-started/heroku-simple.html
You should have a Hasura console that looks like this:
For this tutorial we will use a simple database for a billing app:
BIlling app database:
Go to the Data tab in the Hasura console.
Go to the SQL menu on the left.
Copy the SQL script in the textbox and run it.
After creating our database, we return to the GraphiQL tab. The Explorer is a tool that Hasura offers to make life a little easier when building any type of query according to our database. It is how we can observe some queries for our tables have already been created.
Now we will learn different ways to insert data from the simplest to the most complex.
Let’s look at the following query:
1
2
3
4
5
6
7
8
9
10
11
1 mutation simpleInsertMutation {
2 insert_product(objects: {
3 name: "chocolate bar"
4 bar_code: "123456789",
5
}) {
6 affected_rows
7
}
8
}
Line 1: We initialize our mutation with the name we want.
Line 2: We add the mutation we want to perform from the Explorer, in our case insert_product
. The object or objects we want to insert are placed in the objects
variable.
Lines 3-4: We place the values of the columns we want to insert.
Line 6: Here is what the mutation returns, affected_rows
returns how many rows have been affected in our mutation.
In addition to the statement affected_rows
, we can specify that the mutation returns specific fields:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1 mutation simpleInsertMutation {
2 insert_product(objects: {
3 name: "bread"
4 bar_code: "112233445",
5
}) {
6 returning {
7 id
8
}
9
}
10
}
In lines 6-8 we specify that the id generated to the product we insert is returned. It is required to write affected_rows
or returning
statement in all mutations.
The previous two mutations can be easily created with the Explorer.
To insert products from an application in the previous way, a different mutation would have to be written for each product. Luckily we can create a mutation that accepts parameters:
1
2
3
4
5
6
7
8
9
10
11
1 mutation InsertMutationWithParams($name: String!, $bar_code: String!) {
2 insert_product(objects: {
3 name: $name,
4 bar_code: $bar_code,
5
}) {
6 affected_rows
7
}
8
}
To test this mutation in the Hasura console, we place the following query variables in the editor below the query editor:
1 2 3 4
{ "name": "milk", "bar_code": "444444444" }
After the function name, the parameters are placed inside parentheses. These have the following syntax: $<param-name>:<Type>
. The symbol !
indicates that it is required that the parameter has some value.
There is still a problem with the previous mutation. If we already have the product object we have to unpack it to pass each of its attributes to the mutation. With objects with several attributes, this is a big problem. The solution is to pass the entire object as a parameter:
1
2
3
4
5
6
7
1 mutation InsertMutationWithParams($product: product_insert_input!) {
2 insert_product(objects: [$product]) {
3 affected_rows
4
}
5
}
Query Variables:
1 2 3 4 5 6
{ "product": { "name": "orange juice", "bar_code": "777734921" } }
The type <table> _insert_input
is created by Hasura for each table in our database and represents an object that will be inserted into that table. In our case, we are inserting a product object with the restriction that it cannot be null (!
symbol).
Well, here is another problem, what if I want to enter several products with one shot? The solution:
1
2
3
4
5
6
7
1 mutation InsertMutationWithParams($products: [product_insert_input!] !) {
2 insert_product(objects: $products) {
3 affected_rows
4
}
5
}
Query Variables:
1 2 3 4 5 6 7 8 9
{ "products": [{ "name": "water", "bar_code": "123321453" }, { "name": "coca-cola", "bar_code": "552839274" }] }
In this mutation, we pass as a parameter a list of products. The syntax [<object>!]!
means that the parameter is a non-null and non-empty list.
Hasura allows us to do nested object queries between tables with some relationship. Relationships are a way of connecting objects from two connected tables using foreign keys. To create a relationship, we go to the Data tab, we go to the table where we want to create the relationship (we are going to the price table in this tutorial), and then we go to the Relationships tab:
Here you can see two types of relationships:
Object Relationship: When there is a one-to-one or many-to-one relationship.
Array Relationship: When there is a one-to-many relationship.
We create the relationships suggested to us by clicking on the add button of each one. We do this with all the tables.
In the database, each store has its price for the products found in the price table. What we are going to do is add a store and the prices of its products in a single mutation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1 mutation InserMutationWithRelationship($name: String!) {
2 insert_store(objects: {
3 name: $name,
4 prices: {
5 data: [{
6 id_product: 1,
7 price: 2,
8
}, {
9 id_product: 2,
10 price: 1,
11
}]
12
}
13
}) {
14 affected_rows
15
}
16
}
Query Variables:
1 2 3
{ "name": "Luchito's Store" }
What’s new here starts on line 4. The prices relationship refers to the price objects that have the id_store
of the store object from which we refer. In a mutation, with the relation prices you can insert price objects with the id_store
of the store object to be inserted. This query can be improved to insert a variable number of price objects:
1
2
3
4
5
6
7
8
9
10
11
1 mutation InserMutationWithRelationship($name: String!, $price: price_arr_rel_insert_input!) {
2 insert_store(objects: {
3 name: $name,
4 prices: $prices,
5
}) {
6 affected_rows
7
}
8
}
Query Variables:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "Chris' Store",
"prices": {
"data": [
{
"id_product": 1,
"price": 1.5
},
{
"id_product": 2,
"price": 1
}]
}
}
Here the object is an array of a relationship denoted by price_arr_rel_insert_input
.
Suppose our application reads the barcode of a product and needs to show its details:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
1 query readWithRelationship($id_store: Int, $bar_code: String!) { 2 store(where: { id: { _eq: $id_store } }) { 3 name 4 prices(where: { product: { bar_code: { _eq: $bar_code } } }) { 5 product { 6 name 7 bar_code 8 } 9 price 10 } 11 } 12 }
Query Variables:
1
2
3
4
{
"id_store": 2,
"bar_code": "123456789"
}
In this query, with the relationships, we can easily jump from the store to the prices and from there to the product. In lines 2 and 4, the where
statement was placed to filter the query. These filters can be easily assembled from the Explorer.
Subscriptions have the same syntax and structure as a query, with the difference that any change in the database will automatically update the subscription. The previous query can be changed to subscription by changing the word query to subscription on line 1.
Hasura is a quick and good way to build our API with a scalable database manager such as PostgreSQL and with a dynamic and readable endpoint such as GraphQL (RIP REST). Also, Hasura is so flexible and scalable that we could migrate our databases already created in Postgres to Hasura remotely without damaging the operation of an application that is using that database. Hasura also offers us a token user control system with which it allows us to give permissions through roles so that a certain type of user may or may not make a certain type of query.
What is Hasura?
GraphQL
Hasura GraphQL Engine